package pacMan;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JFrame;
import javax.swing.Timer;

/**
 * 
 * Class: PMMainApp
 * Entry point into PacMan game. Initializes game container objects and
 * then passes user input to the GameBoard object.
 *
 * @author Tomas Rivera
 * @version $Id$
 */
@SuppressWarnings("serial")
public class PMMainApp extends JFrame implements KeyListener{
	
	// Total number of "tiles" in the game board
	public static final int GAMEBOARD_WIDTH = 29;
	public static final int GAMEBOARD_HEIGHT = 36;
	
	// Number of "tiles" in the game maze
	public static final int MAZE_WIDTH = 28;
	public static final int MAZE_HEIGHT = 31;
	
	// Default window size parameters.
	public static final int DEFAULT_WINDOW_WIDTH = 580;
	public static final int DEFAULT_WINDOW_X_LOCATION = 100;
	public static final int DEFAULT_WINDOW_Y_LOCATION = 100;
	
	// Number of times that we will redraw the screen each second.
	public static final int SCREEN_REFRESH_RATE_FPS = 60;
	
	public static final String newline = System.getProperty("line.separator");

	//////////////////////////////////////////////////////////
	// Constructor methods
	//////////////////////////////////////////////////////////
	
	/**
	 * Create the application.
	 */
	public PMMainApp() {
		initializeWindow();
		initializeControls();
		initializeGameObjects();
		
		screenDrawTimer = new Timer(1000 / SCREEN_REFRESH_RATE_FPS, (e) -> drawNextFrame());
		screenDrawTimer.start();
	}

	//////////////////////////////////////////////////////////
	// Public methods
	//////////////////////////////////////////////////////////
	
	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					PMMainApp window = new PMMainApp();
					window.setVisible(true);
					System.out.println("start");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
	
	//////////////////////////////////////////////////////////
	// Overridden methods
	//////////////////////////////////////////////////////////
	
	/*
	 * (non-Javadoc)
	 * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
	 */
	@Override
	public void keyTyped(KeyEvent e) {
		displayInfo(e, "Key Typed: ");
		switch(e.getKeyChar()) {
			case '+':
				if(windowParams.getWindowWidth() / GAMEBOARD_WIDTH >= 32) {
					return;
				}
				if(!gamePaused) {
					screenDrawTimer.stop();
				}
				windowParams.setWindowWidth(((windowParams.getWindowWidth() / GAMEBOARD_WIDTH) + 4) * GAMEBOARD_WIDTH);
				windowParams.setWindowHeight(windowParams.getWindowWidth() / GAMEBOARD_WIDTH * GAMEBOARD_HEIGHT);
				windowParams.setPixelScale(windowParams.getWindowWidth() / GAMEBOARD_WIDTH);
				windowParams.setPixelOffsetX(windowParams.getWindowWidth() / GAMEBOARD_WIDTH);
				windowParams.setPixelOffsetY(windowParams.getWindowWidth() / GAMEBOARD_WIDTH * 3);
				this.setSize(windowParams.getWindowWidth(), windowParams.getWindowHeight());
				
				System.out.printf("New size: width=%d, height=%d, actor=%d%n", 
						windowParams.getWindowWidth(), windowParams.getWindowHeight(), windowParams.getActorScale());
				
				gameBoard.resizeMaze();
				if(!gamePaused) {
					screenDrawTimer.start();
				}
				return;
			case '-':
				if(windowParams.getWindowWidth() / GAMEBOARD_WIDTH <= 16) {
					return;
				}
				if(!gamePaused) {
					screenDrawTimer.stop();
				}
				windowParams.setWindowWidth(((windowParams.getWindowWidth() / GAMEBOARD_WIDTH) - 4) * GAMEBOARD_WIDTH);
				windowParams.setWindowHeight(windowParams.getWindowWidth() / GAMEBOARD_WIDTH * GAMEBOARD_HEIGHT);
				windowParams.setPixelScale(windowParams.getWindowWidth() / GAMEBOARD_WIDTH);
				windowParams.setPixelOffsetX(windowParams.getWindowWidth() / GAMEBOARD_WIDTH);
				windowParams.setPixelOffsetY(windowParams.getWindowWidth() / GAMEBOARD_WIDTH * 3);
				this.setSize(windowParams.getWindowWidth(), windowParams.getWindowHeight());
				
				System.out.printf("New size: width=%d, height=%d, actor=%d%n", 
						windowParams.getWindowWidth(), windowParams.getWindowHeight(), windowParams.getActorScale());
				
				gameBoard.resizeMaze();
				if(!gamePaused) {
					screenDrawTimer.start();
				}
				return;
			default:
				break;
		}
		
		
		if(gameBoard.getGameState() == GameStateManager.GAME_STATE.DEMO_SEQUENCE) {
			gameBoard.startNewGame();
			return;
		}
		
		if(gameBoard.getGameState() == GameStateManager.GAME_STATE.WAIT_STARTING) {
			return;
		}

		
		switch(e.getKeyChar()) {
			case '1':
				gameBoard.debug_changeGhostState(GameStateManager.MOVE_BEHAVIOR.SCATTER);
				break;
			case '2':
				gameBoard.debug_changeGhostState(GameStateManager.MOVE_BEHAVIOR.CHASE);
				break;
			case '3':
				gameBoard.debug_toggleEnergizerConsumed();
				break;
			case '4':
				gameBoard.debug_roundComplete();
				break;
			case 'r':
				gameBoard.releaseGhost();
				break;
			case 'p':
				// Pause character movement and music
				displayInfo(e, "Key Typed: ");
				togglePause();
				break;
			case 'k':
				// Simulate player killed
				
			case ' ':
			case '\n':
				displayInfo(e, "Key Typed: ");
				// Start new game 
				break;
			default:
				break;
		}
	}

	/*
	 * (non-Javadoc)
	 * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
	 */
	@Override
	public void keyPressed(KeyEvent e) {
		// TODO Auto-generated method stub
		//displayInfo(e, "Key Pressed: ");
		switch(e.getKeyCode()) {
			case KeyEvent.VK_UP:
			case KeyEvent.VK_KP_UP:
			case KeyEvent.VK_C:
				if(!moveUpPressed) {
					gameBoard.playerRequestDirectionChange(CardinalDirection.NORTH);
					moveUpPressed = true;
				}
				break;
				
			case KeyEvent.VK_DOWN:
			case KeyEvent.VK_KP_DOWN:
			case KeyEvent.VK_D:
				if(!moveDownPressed) {
					gameBoard.playerRequestDirectionChange(CardinalDirection.SOUTH);
					moveDownPressed = true;
				}
				break;
				
			case KeyEvent.VK_LEFT:
			case KeyEvent.VK_KP_LEFT:
			case KeyEvent.VK_E:
				if(!moveLeftPressed) {
					gameBoard.playerRequestDirectionChange(CardinalDirection.WEST);
					moveLeftPressed = true;
				}
				break;
				
			case KeyEvent.VK_RIGHT:
			case KeyEvent.VK_KP_RIGHT:
			case KeyEvent.VK_F:
				if(!moveRightPressed) {
					gameBoard.playerRequestDirectionChange(CardinalDirection.EAST);
					moveRightPressed = true;
				}
				break;

			default:
				break;
		}
	}

	/*
	 * (non-Javadoc)
	 * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
	 */
	@Override
	public void keyReleased(KeyEvent e) {
		//displayInfo(e, "Key Released: ");
		switch(e.getKeyCode()) {
			case KeyEvent.VK_UP:
			case KeyEvent.VK_KP_UP:
			case KeyEvent.VK_C:
				if(moveDownPressed) {
					gameBoard.playerRequestDirectionChange(CardinalDirection.SOUTH);
				} else {
					gameBoard.playerRequestDirectionChange(CardinalDirection.VNONE);
				}
				
				moveUpPressed = false;
				break;
			
			case KeyEvent.VK_DOWN:
			case KeyEvent.VK_KP_DOWN:
			case KeyEvent.VK_D:
				if(moveUpPressed) {
					gameBoard.playerRequestDirectionChange(CardinalDirection.NORTH);
				} else {
					gameBoard.playerRequestDirectionChange(CardinalDirection.VNONE);
				}
				
				moveDownPressed = false;
				break;
				
			case KeyEvent.VK_LEFT:
			case KeyEvent.VK_KP_LEFT:
			case KeyEvent.VK_E:
				if(moveRightPressed) {
					gameBoard.playerRequestDirectionChange(CardinalDirection.EAST);
				} else {
					gameBoard.playerRequestDirectionChange(CardinalDirection.HNONE);
				}
				
				moveLeftPressed = false;
				break;
				
			case KeyEvent.VK_RIGHT:
			case KeyEvent.VK_KP_RIGHT:
			case KeyEvent.VK_F:
				if(moveLeftPressed) {
					gameBoard.playerRequestDirectionChange(CardinalDirection.WEST);
				} else {
					gameBoard.playerRequestDirectionChange(CardinalDirection.HNONE);
				}
				
				moveRightPressed = false;
				break;

		default:
			break;
		}
	}
	
	//////////////////////////////////////////////////////////
	// Private methods
	//////////////////////////////////////////////////////////
	
	/*
	 * Initialize the JFrame and configure all size parameters.
	 */
	private void initializeWindow() {
		// Set window title
		this.setTitle("PacMan");
		
		// Set window parameters
		windowWidth = DEFAULT_WINDOW_WIDTH;
		windowLocX = DEFAULT_WINDOW_X_LOCATION;
		windowLocY = DEFAULT_WINDOW_Y_LOCATION;
		
		windowParams = new WindowParameters(
				windowWidth, 
				windowWidth / GAMEBOARD_WIDTH * GAMEBOARD_HEIGHT,
				windowWidth / GAMEBOARD_WIDTH, 
				windowWidth / GAMEBOARD_WIDTH, 
				windowWidth / GAMEBOARD_WIDTH * 3, Color.BLUE);
		
		this.setBounds(windowLocX, windowLocY, windowParams.getWindowWidth(), windowParams.getWindowHeight());
		
	    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setResizable(false);
	}
	
	/*
	 * Configure user controls.
	 */
	private void initializeControls() {
		// Initialize player controls
		moveUpPressed = false;
		moveDownPressed = false;
		moveLeftPressed = false;
		moveRightPressed = false;
		this.addKeyListener(this);
	}

	/*
	 * Creates and initializes game elements:
	 * 	- ScoreTracker
	 * 		Manages game score for player.
	 *  - ObjectLocationTracker
	 *  	Tracks location of stationary objects on screen and determines allowable movements for Actors.
	 *  - GameBoard
	 *  	Manages game state
	 */
	private void initializeGameObjects() {
		// Initialize sound effects
		SoundEffect.init();

		// Create ScoreTracker object. This 
		scoreTracker = new ScoreTracker(0, 0);
		
		// Build the Maze
		objectLocationTracker = MazeBuilder.getInstance().createMaze(windowParams, scoreTracker, "/defaultMaze", MAZE_WIDTH, MAZE_HEIGHT);
		
		// Initialize the GameBoard
		gameBoard = new GameBoard(windowParams, scoreTracker, objectLocationTracker);
		
		this.getContentPane().add(gameBoard, BorderLayout.CENTER);
		gameBoard.setBackground(Color.BLACK);
	}
	
	/*
	 *  Manages pausing game by preventing movement of characters on screen. 
	 */
	private void togglePause() {
		gameBoard.togglePause();
	}
	
	
	/*
	 * All character animation is synchronized based on screen refresh rate of
	 * 60 frames per second.
	 * 
	 */
	private void drawNextFrame() {
		if(!gamePaused) {
			gameBoard.moveActorsAndRepaint();
		}
	}
	
	/*
	 * (DEBUG) Prints out information about the KeyEvent.
	 * 
	 */
	private void displayInfo(KeyEvent e, String keyStatus){
        
        //You should only rely on the key char if the event
        //is a key typed event.
        int id = e.getID();
        String keyString;
        if (id == KeyEvent.KEY_TYPED) {
            char c = e.getKeyChar();
            keyString = "key character = '" + c + "'";
        } else {
            int keyCode = e.getKeyCode();
            keyString = "key code = " + keyCode
                    + " ("
                    + KeyEvent.getKeyText(keyCode)
                    + ")";
        }
         
        int modifiersEx = e.getModifiersEx();
        String modString = "extended modifiers = " + modifiersEx;
        String tmpString = KeyEvent.getModifiersExText(modifiersEx);
        if (tmpString.length() > 0) {
            modString += " (" + tmpString + ")";
        } else {
            modString += " (no extended modifiers)";
        }
         
        String actionString = "action key? ";
        if (e.isActionKey()) {
            actionString += "YES";
        } else {
            actionString += "NO";
        }
         
        String locationString = "key location: ";
        int location = e.getKeyLocation();
        if (location == KeyEvent.KEY_LOCATION_STANDARD) {
            locationString += "standard";
        } else if (location == KeyEvent.KEY_LOCATION_LEFT) {
            locationString += "left";
        } else if (location == KeyEvent.KEY_LOCATION_RIGHT) {
            locationString += "right";
        } else if (location == KeyEvent.KEY_LOCATION_NUMPAD) {
            locationString += "numpad";
        } else { // (location == KeyEvent.KEY_LOCATION_UNKNOWN)
            locationString += "unknown";
        }
        
        System.out.println(keyStatus + newline
                + "    " + keyString + newline
                + "    " + modString + newline
                + "    " + actionString + newline
                + "    " + locationString + newline);
    }
	
	
	
	// These manage user direction button presses.
	private boolean moveUpPressed;
	private boolean moveDownPressed;
	private boolean moveLeftPressed;
	private boolean moveRightPressed;
	
	// For determining screen size and x,y location on screen.
	private int windowWidth;
	private int windowLocX;
	private int windowLocY;
	
	// Game elements.
	private GameBoard gameBoard;
	private ScoreTracker scoreTracker;
	private WindowParameters windowParams;
	private ObjectLocationTracker objectLocationTracker;
	private Timer screenDrawTimer;
	private boolean gamePaused;
}
